package org.softee.functional;

import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import org.softee.functional.functions.BinaryOperator;
import org.softee.functional.functions.Block;
import org.softee.functional.functions.Mapper;
import org.softee.functional.functions.Predicate;

/**
 * Inspired by JDK 8 java.lang.Iterable and java.util.ParallelIterable.
 *
 * Inspired by:
 * <ul>
 * <li>Brian Goetz' <a href="http://cr.openjdk.java.net/~briangoetz/lambda/collections-overview.html">
 * State of the Lambda: Libraries Edition</a></li>
 * <li><a href="http://www.dalorzo.com/jdk8/javadocs/">JDK 8 JavaDoc</a></li>
 * <li>http://code.google.com/p/functional-collections/</li>
 *
 * To obtain an instance, use static factory methods if concrete classes
 *
 * @author morten.hattesen@gmail.com
 */
public interface FunctionalIterable<T> extends Iterable<T> {

    boolean allMatch(Predicate<? super T> filter);
    boolean anyMatch(Predicate<? super T> filter);
    boolean noneMatch(Predicate<? super T> filter);

    /**
     * @return true if this iterable is empty (contains no elements)
     */
    boolean isEmpty();

    /**
     * Filter elements according to the provided predicate and return a an Iterable view of the filtered elements.
     * The filtered view will reflect changes in this Iterable.
     * @param predicate Decides which elements should be included in the resulting Iterable view.
     * Each element with a true result will be included in the resulting view.
     * @return An Iterable view of the filtered elements.
     */
    FunctionalIterable<T> filter(Predicate<? super T> predicate);
    // TODO: should be called unique()?
    FunctionalIterable<T> uniqueElements();
    // TODO: Implement using a call to filter() using a memoizing predicate
    FunctionalIterable<T> removeDuplicates();
    FunctionalIterable<T> sorted();
    FunctionalIterable<T> sorted(Comparator<? super T> comparator);
    <U extends Comparable<? super U>> FunctionalIterable<T> sortedBy(Mapper<? super T, U> extractor);

    /**
     * Performs the operation specified by block upon each element
     * @param operation The operation to be performed upon each each element
     */
    FunctionalIterable<T> forEach(Block<? super T> block);

    /**
     * Map elements using the provided mapper.
     * The mapped view will reflect changes in this Iterable.
     * @param mapper Performs the mapping between elements of type T and elements of type U
     * @param <U> Type of the returned elements
     * @return An Iterable view consisting of the mapped elements
     */
    <U> FunctionalIterable<U> map(Mapper<? super T, ? extends U> mapper);

    /**
     * Map elements using the provided mapper.
     * The mapped view will reflect changes in this Iterable.
     * @param mapper Performs the mapping between elements of type T and zero to infinite elements  of type U
     * @return An Iterable view consisting of the mapped elements
     */
    <U> FunctionalIterable<U> flatMap(Mapper<? super T,? extends Iterable<U>> mapper);

    <U> Map<T,U> mapped(Mapper<? super T,? extends U> mapper);

    // TODO change signature from Collection<T> to Iterable<T> in line with JDK 8 proposal
    <U> Map<U,Collection<T>> groupBy(Mapper<? super T,? extends U> mapper);

    /**
     * Eager operation.
     * @param target The collection to which the elements are added
     * @param <A> Type of target collection
     */
    <A extends Collection<? super T>> A into(A target);

    /**
     * @return the elements of this iterable as a List
     */
    // TODO: is this required? method name?
    List<T> asList();

    /**
     * Returns an element from this Iterable. Depending on the source type of this Iterable repeated calls may-or-may-not return the same element. If order is not important this method may be more efficient than getfirst.
     * @return an element of the collection
     * @throws NoSuchElementException if this Iterable contains no elements.
     */
    T getAny();

    /**
     * Returns the first element from this Iterable. Depending on the source type of this Iterable repeated calls may-or-may-not return the same element.
     * @return the first element of the Iterable
     * @throws NoSuchElementException if this Iterable contains no elements.
     */
    T getFirst();

    /**
     * Returns the first item from this Iterable and require that there be no other elements in this Iterable.
     * @return the element of the Iterable
     * @throws NoSuchElementException if this Iterable contains no elements
     * @throws IllegalStateException if this Iterable contains more than one element.
     */
    T getOnly();

    /**
     * Reduce elements to a single value
     * @param base Initial value for reducer
     * @param reducer Reduces two elements of type T to a single value of type T
     * @return
     */
    T reduce(T base, BinaryOperator<T> reducer);

    /**
     * Combined map and reduce operation
     * @param mapper Performs the mapping between elements of type T and elements of type U
     * @param base Initial value for reducer
     * @param reducer Reduces two elements of type U to a single value of type U
     * @param <U> Type of the returned value
     * @return
     */
    <U> U mapReduce(Mapper<? super T,? extends U> mapper, U base, BinaryOperator<U> reducer);
}
